# -*- coding: utf-8 -*-
import win32serviceutil
import win32service
import win32event

VERSION = '2.5'
SOCKET_TIMEOUT = 5
ACARD_CLIENT_DEBUG = False
ACARD_PERIOD = 300

'''
history:
    2015-6-22:  V1.0,初始版本
    2015-7-6:   V1.1,增加漂移IP在线状态监测
    2015-7-7:   V1.2,修改读卡器正常判断为检测读卡器的服务器地址及端口号设置是否正确
    2015-7-8:   V1.3,增加主程序版本号检测
    2015-7-16:  V1.4,漂移IP增加监测主备IP地址
    2015-7-16:  V1.5,修复漂移IP监测bug
    2015-7-17:  V1.6,增加已考勤/未考勤数量统计
    2015-7-20:  V1.7,修复已考勤/未考勤数量统计提交问题
    2015-8-4:   V1.8,增加服务IP状态检查
    2015-10-15: V1.9,优化3次POST提交,优化CPU使用率获取
    2015-11-17: V1.10,修复C#客户端版本号读取错误
    2015-11-18: V2.0,由于客户端版本判断问题,忽略1.10版本,直接升级为2.0
    2015-11-18: V2.1,执行自动化命令autocmd
    2016-1-4:   V2.2,增加自动修复数据库索引错误功能
    2016-1-20:  V2.3,修复数据库路径错误
    2016-2-1:   V2.4,增加上传考勤客户端日志功能
    2016-2-16:  V2.5,修改上传考勤日志名称为工控机mac,增加上传本地时间功能
'''


posturl_list = [
    "http://115.182.69.8:8102/recv/acard"
    #"http://182.92.170.145:8102/recv/acard"
]
posturl = "http://115.182.69.8:8102/recv/acard"

def post(url, data):
    import urllib
    import urllib2
    data = urllib.urlencode(data)
    if ACARD_CLIENT_DEBUG:
        print data
    req = urllib2.Request(url, data)
    for i in range(3):
        try:
            response = urllib2.urlopen(req, timeout=5)
            return response.read()
        except:
            continue
    return 'open "%s" error' % (url)

def get_cpu():
    import wmi
    c = wmi.WMI()
    data_dict = []
    for cpu in c.Win32_Processor():
        device = cpu.DeviceID.lower()
        # Get cpu_usage
        if ACARD_CLIENT_DEBUG:
            print cpu
        if not cpu.LoadPercentage:
            data_dict.append(1.0)
        else:
            data_dict.append(float(cpu.LoadPercentage))
    return sum(data_dict) / len(data_dict)

def get_mem():
    import wmi
    c = wmi.WMI ()
    cs = c.Win32_ComputerSystem()
    os = c.Win32_OperatingSystem()
    #pfu = c.Win32_PageFileUsage()
    #data_dict = {}
    #data_dict["MemTotal"] = {'volume':float(cs[0].TotalPhysicalMemory) / (1024*1024), 'unit':'MB'}
    #data_dict["MemFree"] = {'volume':float(os[0].FreePhysicalMemory)/1024, 'unit':'MB'}
    #data_dict["SwapTotal"] = {'volume':float(pfu[0].AllocatedBaseSize), 'unit':'MB'}
    #data_dict["SwapFree"] = {'volume':float(pfu[0].AllocatedBaseSize - pfu[0].CurrentUsage), 'unit':'MB'}
    #return {'data':data_dict, 'timestamp':time.asctime(time.localtime())}
    total_mem = float(cs[0].TotalPhysicalMemory)
    free_mem = float(os[0].FreePhysicalMemory) * 1024
    return round((1 - free_mem / total_mem) * 100, 2)

def get_disk():
    import wmi
    c = wmi.WMI ()

    data_dict = {}
    data_dict['total_available'] = 0
    data_dict['total_capacity'] = 0
    data_dict['total_free'] = 0

    #  DriveType=3 : "Local Disk",
    for disk in c.Win32_LogicalDisk(DriveType=3):
        #data_dict['total_available'] += round(float(disk.FreeSpace) / (1024*1024*1024), 2)
        data_dict['total_capacity'] += float(disk.Size) / (1024*1024*1024)
        data_dict['total_free'] += float(disk.FreeSpace) / (1024*1024*1024)
    '''
        dev_tmp = {}
        dev_tmp['dev'] = disk.DeviceID
        dev_tmp['available'] = {'volume':round(float(disk.FreeSpace) / (1024*1024*1024), 2), 'unit':'GB'}
        dev_tmp['capacity'] = {'volume':round(float(disk.Size) / (1024*1024*1024), 2), 'unit':'GB'}
        dev_tmp['free'] = {'volume':round(float(disk.FreeSpace) / (1024*1024*1024), 2), 'unit':'GB'}
        dev_tmp['fstype'] = disk.FileSystem
        dev_tmp['mnt'] = ''
        dev_tmp['used'] = round(long(disk.FreeSpace) / long(disk.Size), 2)
        data_dict[disk.DeviceID] = dev_tmp
    com = client.Dispatch("WbemScripting.SWbemRefresher")
    obj = client.GetObject("winmgmts:\\root\cimv2")
    diskitems = com.AddEnum(obj, "Win32_PerfFormattedData_PerfDisk_LogicalDisk").objectSet

    com.Refresh()
    for item in diskitems:
        if item.Name in data_dict:
            data_dict[item.Name]['io_stat'] = {}
            data_dict[item.Name]['io_stat']['r/s'] = {'volume':float(item.DiskReadsPerSec), 'unit':''}
            data_dict[item.Name]['io_stat']['w/s'] = {'volume':float(item.DiskWritesPerSec), 'unit':''}
            data_dict[item.Name]['io_stat']['rkB/s'] = {'volume':(float(item.DiskReadBytesPerSec) / 1024), 'unit':'KB/s'}
            data_dict[item.Name]['io_stat']['wkB/s'] = {'volume':(float(item.DiskWriteBytesPerSec) / 1024), 'unit':'KB/s'}
    #return {'data':data_dict, 'timestamp':time.asctime(time.localtime())}
    '''
    return round((1 - data_dict['total_free'] / data_dict['total_capacity']) * 100, 2)

def get_network(balance_ip):
    import wmi
    c = wmi.WMI()
    for interface in c.Win32_NetworkAdapterConfiguration(IPEnabled=1):
        #print interface.Description, interface.MACAddress
        for ip_address in interface.IPAddress:
            #print ip_address
            if ip_address == balance_ip:
                return True
    return False

def port_is_open(ip, port):
    import socket
    try:
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.settimeout(1)
        s.connect((ip, int(port)))
        s.shutdown(2)
        if ACARD_CLIENT_DEBUG:
            print '%s:%d is open' % (ip, port)
        return True
    except:
        if ACARD_CLIENT_DEBUG:
            print '%s:%d is down' % (ip, port)
        return False

def balance_ip_is_alive(ip, port):
    master_ip = ip.split('.')[:3]
    master_ip.append('98')
    master_ip = '.'.join(master_ip)
    slaver_ip = ip.split('.')[:3]
    slaver_ip.append('99')
    slaver_ip = '.'.join(slaver_ip)
    if ACARD_CLIENT_DEBUG:
        print 'master_ip', master_ip
        print 'slaver_ip', slaver_ip
    if port_is_open(master_ip, port) and port_is_open(slaver_ip, port) and port_is_open(ip, port):
        return True
    else:
        return False

def ip_is_alive(ip):
    import ping
    try:
        percent, max_tm, avrg_tm = ping.quiet_ping(ip)
        if ACARD_CLIENT_DEBUG:
            print 'ping', 'percent:', percent, 'max_tm:', max_tm, 'avrg_tm:', avrg_tm

        if percent == 100:
            return False
        else:
            return True
    except:
        return False

def proc_is_alive(processname):
    import os
    tasklist = os.popen('tasklist').read().split('\n')
    for task in tasklist:
        #print task
        if task and task.find(processname) >= 0:
            #print task
            return True
    return False

def get_city_venue_ctm_cardreader():
    import MySQLdb
    import time
    conn = MySQLdb.connect(host='localhost', user='root', passwd='123456', db='cardpool')
    cur = conn.cursor()
    # get area_code, venue_id, mac
    cur.execute('select * from tb_user_info')
    user_ret = cur.fetchall()[0]
    user_info = ('id', 'venue_id', 'area_code', 'create_date', 'is_deleted', 'modify_date', 'mac')
    user_info_dict = dict(zip(user_info, user_ret))
    # get cardreader ip
    cur.execute('select * from tb_card_reader_info')    #(id,ip,remark,create_date,is_deleted)
    cardreader_ret = cur.fetchall()
    cardreader = []
    for i in cardreader_ret:
        cardreader.append(i[1])     # add ip to cardreader list
    user_info_dict['cardreader'] = cardreader
    # get the balance ipaddr
    balance_ip = cardreader[0]
    _ip_split = balance_ip.split('.')
    _ip_split[3] = '100'
    balance_ip = '.'.join(_ip_split)
    user_info_dict['balance_ip'] = balance_ip
    # get prepare attendance data
    cur.execute('select class_date from tb_prepare_attendance order by class_date')
    pre_attend_ret = cur.fetchall()
    user_info_dict['attend_num'] = len(pre_attend_ret)
    tm_sec = 0.0
    if user_info_dict['attend_num'] > 0:
        tm_date = pre_attend_ret[0][0].strftime("%Y-%m-%d")
        tm_time = time.strptime(tm_date, '%Y-%m-%d')
        tm_sec = time.mktime(tm_time)
    user_info_dict['class_date'] = str(tm_sec)
    # get attend/noattend/total
    cur.execute('select COUNT(CASE WHEN tt.`is_absent` = 0 THEN 1 ELSE NULL END) AS attend, COUNT(CASE WHEN tt.`is_absent` = 1 THEN 1 ELSE NULL END) AS noattend, COUNT(1) AS total FROM tb_prepare_attendance tt WHERE tt.`is_deleted` = 0')
    attend_count_ret = cur.fetchall()[0]
    user_info_dict['attend'] = attend_count_ret[0]
    user_info_dict['noattend'] = attend_count_ret[1]
    user_info_dict['total'] = attend_count_ret[2]
    if ACARD_CLIENT_DEBUG:
        print user_info_dict
    return user_info_dict

def cardreader_ping(cr_ip):
    import socket
    from struct import unpack
    addrc = (cr_ip, 8013)
    #wdata = '\xab\xaa\x4a\x00\xb0\x0a\x83\x00\xd0\xa2'
    wdata = '\xab\xf0\x05\x02\x60\x0a\x85\x00\x20\x93'      # cmd 0x85
    ret = False
    try:
        s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
        s.settimeout(1)
        s.sendto(wdata, addrc)
        rdata, addrs = s.recvfrom(1024)
        if rdata:
            head, serial, size, cmd, ret = unpack('1s4s1s1s1s', rdata[:8])
            if ret == '\x00':
                srv_ip = rdata[8:].split('\x00')[0]
                srv_port, srv_mode = unpack('<HB', rdata[60:63])
                #print srv_ip, srv_port, srv_mode
                cr_ip_t = cr_ip.split('.')
                sr_ip_t = srv_ip.split('.')
                if cr_ip_t[2] == sr_ip_t[2] and sr_ip_t[3] == '100':
                    ret = True
        s.close()
        return ret
    except Exception as e:
        #print 'error:', str(e)
        return False

def get_cardreader(cardreader_list):
    import json
    ret = []
    for cr_ip in cardreader_list:
        r = cardreader_ping(cr_ip)
        ret.append((cr_ip, r))
    return json.dumps(ret)

def get_app_ver():
    try:
        app_ver = open('D:\Program Files (x86)\Attend\AttendManage\Update\Version.txt').read()
        if len(app_ver) > 3 and app_ver[:3] == '\xef\xbb\xbf':
            app_ver = app_ver[3:]
        if ACARD_CLIENT_DEBUG:
            print 'app_ver:', app_ver
        return app_ver
    except Exception as e:
        return '1.0'

def fix_mysql_index_error():
    import os
    print 'running fix_mysql_index_error() ...'
    os.chdir(r'C:\ProgramData\MySQL\MySQL Server 5.5\data\cardpool')
    os.system(r'"d:\Program Files (x86)\MySQL\MySQL Server 5.5\bin\myisamchk.exe" -c -r tb_prepare_log')
    os.system(r'"d:\Program Files (x86)\MySQL\MySQL Server 5.5\bin\myisamchk.exe" -c -r tb_prepare_attendance')
    os.system(r'"d:\Program Files (x86)\MySQL\MySQL Server 5.5\bin\myisamchk.exe" -c -r tb_card_swing_log')

def upload_ams_log(venue_id, localpath, remotepath):
    from ftplib import FTP
    from datetime import date
    ftp_server = '115.182.69.8'
    #ftp_server = '47.88.24.35'
    username = 'qujianning'
    password = 'Ko0f3Hf9Jh31Q'
    bufsize = 1024
    
    date_path = date.today().isoformat()
    remote_fname = 'attend_%s.log' % venue_id

    ftp = FTP()
    #ftp.set_debuglevel(2)
    ftp.connect(ftp_server, 21)
    ftp.login(username, password)
    ftp.cwd(remotepath)
    try:
        ftp.mkd(date_path)
    except:
        pass
        #print 'the dir is exists %s' % date_path
    ftp.cwd(date_path)
    fp = open(localpath,'rb')
    ftp.storbinary('STOR '+ remote_fname, fp, bufsize)
    ftp.set_debuglevel(0)
    fp.close()
    ftp.quit()

def attend_log_upload(venue_id):
    import datetime as dt
    import os
    localpath = r'D:\Program Files (x86)\Attend\AttendManage\log\attend.log'
    remotepath = "prj/tdm/log"
    today = dt.date.today()
    upload_tm = dt.datetime(today.year, today.month, today.day, 23, 0, 0)
    current_tm = dt.datetime.now()
    delta_tm = current_tm - upload_tm
    #print delta_tm.seconds
    if (0 < delta_tm.seconds < ACARD_PERIOD):
        print 'upload the attend log:', venue_id
        upload_ams_log(venue_id, localpath, remotepath)
        os.remove(localpath)

'''
1,CPU,内存和硬盘使用率(ok);
2,3306,9100端口号(ok);
3,统计考勤表数据,数据表
4,应程序的版本号
5,教学点id,城市,设备名称
6,刷卡机通信状态
'''
def acard_post():
    import json
    import datetime
    import socket
    print 'VERSION:', VERSION
    socket.setdefaulttimeout(SOCKET_TIMEOUT)
    try:
        ctm_info = get_city_venue_ctm_cardreader()
    except Exception as e:
        print 'get_city_venue_ctm_cardreader error:', str(e)
        fix_mysql_index_error()
        return 'fix_mysql_index_error'
    data = {}
    data['city'] = ctm_info['area_code']
    data['venue'] = ctm_info['venue_id']
    data['ctm'] = ctm_info['mac']
    data['status'] = 1
    data['cpu'] = get_cpu()
    data['memory'] = get_mem()
    data['disk'] = get_disk()
    data['app_live'] = proc_is_alive("CardPoolView.exe")
    data['app_ver'] = get_app_ver()
    data['port_9100'] = port_is_open("127.0.0.1", 9100)
    data['port_3306'] = port_is_open("127.0.0.1", 3306)
    data['master_slaver'] = balance_ip_is_alive(ctm_info['balance_ip'], 9100)
    data['ip_100'] = get_network(ctm_info['balance_ip'])
    data['cardreader'] = get_cardreader(ctm_info['cardreader'])
    data['class_date'] = ctm_info['class_date']
    data['attend_num'] = ctm_info['attend_num']
    data['attend'] = ctm_info['attend']
    data['noattend'] = ctm_info['noattend']
    data['total'] = ctm_info['total']
    data['cli_ver'] = VERSION
    data['date'] = datetime.date.today().isoformat()
    #ret = []
    #for posturl in posturl_list:
    #    ret.append({posturl: post(posturl, data)})
    try:
        ret = json.loads(post(posturl, data))
        result = ret.get('result', '')
        cmd = ret.get('cmd', '')
        if cmd:
            exec(cmd)
    except Exception as e:
        err_msg = "acard_post error: " + str(e)
        return err_msg
    try:
        attend_log_upload(data['ctm'])
    except Exception as e:
        print 'attend_log_upload() error:' + str(e)
    return '%s: result:%s, cmd:%s' % (data['ctm'], ret.get('result'), ret.get('cmd'))


class PythonService(win32serviceutil.ServiceFramework):
    """
    Usage: 'PythonService.py [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'
    Options for 'install' and 'update' commands only:
     --username domain\username : The Username the service is to run under
     --password password : The password for the username
     --startup [manual|auto|disabled|delayed] : How the service starts, default = manual
     --interactive : Allow the service to interact with the desktop.
     --perfmonini file: .ini file to use for registering performance monitor data
     --perfmondll file: .dll file to use when querying the service for
       performance data, default = perfmondata.dll
    Options for 'start' and 'stop' commands only:
     --wait seconds: Wait for the service to actually start or stop.
                     If you specify --wait with the 'stop' option, the service
                     and all dependent services will be stopped, each waiting
                     the specified period.
    """
    #服务名
    _svc_name_ = "PythonService"
    #服务显示名称
    _svc_display_name_ = "PythonService TDM"
    #服务描述
    _svc_description_ = "学而思TDM监控系统,考勤中控机监控"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        self.logger = self._getLogger()
        self.isAlive = True

    def _getLogger(self):
        import logging
        import os
        import inspect

        logger = logging.getLogger('[PythonService-tdm]')

        this_file = inspect.getfile(inspect.currentframe())
        dirpath = os.path.abspath(os.path.dirname(this_file))
        handler = logging.FileHandler(os.path.join(dirpath, "tdm_service.log"))

        formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
        handler.setFormatter(formatter)

        logger.addHandler(handler)
        logger.setLevel(logging.INFO)

        return logger

    def SvcDoRun(self):
        import time
        self.logger.info("python tdm_svc do run....")
        while self.isAlive:
            self.logger.info("I am alive.")
            try:
                ret = acard_post()
                self.logger.info(ret)
            except Exception as e:
                self.logger.error(str(e))
            time.sleep(ACARD_PERIOD)
        # 等待服务被停止
        #win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)

    def SvcStop(self):
        # 先告诉SCM停止这个过程
        self.logger.info("python tdm_svc do stop....")
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        # 设置事件
        win32event.SetEvent(self.hWaitStop)
        self.isAlive = False

if __name__=='__main__':
    #win32serviceutil.HandleCommandLine(PythonService)
    import time
    while True:
        try:
            ret = acard_post()
            print ret
        except Exception as e:
            print "error:", str(e)
        time.sleep(ACARD_PERIOD)
